package com.chunlin.exception;

import java.io.IOException;

public class ExceptionTest {
    public static void test1() {
        //异常可能在编译时发生，也可能在程序运行时发生，根据发生的时机不同，可以将异常分为：
        //1. 编译时异常
        //在程序编译期间发生的异常，称为编译时异常，也称为受检查异常(Checked Exception)
        //2. 运行时异常
        //在程序执行期间发生的异常，称为运行时异常，也称为非受检查异常(Unchecked Exception)
        //RunTimeException以及其子类对应的异常，都称为运行时异常
        // 比如：NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException
        //注意：编译时出现的语法性错误，不能称之为异常。例如将 System.out.println 拼写错了, 写成了system.out.println
        // 此时编译过程中就会出错, 这是 "编译期" 出错。而运行时指的是程序已经编译通过得到class文件了, 再由 JVM 执行过程中出现的错误
    }

    //实现一个获取数组中任意位置元素的方法
    public static int getElement(int[] array, int index) {
        if (array == null) {
            throw new NullPointerException("传递数组为null");
        }

        if (index < 0 || index >= array.length) {
            throw new ArrayIndexOutOfBoundsException("数组下标越界");
        }

        return array[index];
    }

    //异常的抛出
    //在Java中，可以借助throw关键字，抛出一个指定的异常对象，将错误信息告知给调用者
    public static void test2() {
//        int[] array = new int[]{1, 2, 3};
        int[] array = null;
        System.out.println(getElement(array, 1));

        //【注意事项】
        //1. throw必须写在方法体内部
        //2. 抛出的对象必须是Exception 或者 Exception 的子类对象
        //3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类，则可以不用处理，直接交给JVM来处理(基本就是终止线程)
        //4. 如果抛出的是编译时异常，用户必须处理，否则无法通过编译
        //5. 异常一旦抛出，其后的代码就不会执行
    }

    //异常的捕获
    //异常的捕获，也就是异常的具体处理方式，主要有两种：异常声明throws 以及 try-catch捕获处理
    //异常声明throws
    //处在方法声明时参数列表之后，当方法中抛出编译时异常，用户不想处理该异常，此时就可以借助throws将异常抛
    //给方法的调用者来处理。即当前方法不处理异常，提醒方法的调用者处理异常
    //【注意事项】
    //1. throws必须跟在方法的参数列表之后
    //2. 声明的异常必须是 Exception 或者 Exception 的子类
    //3. 方法内部如果抛出了多个异常，throws之后必须跟多个异常类型，之间用逗号隔开，如果抛出多个异常类型具有父子关系，直接抛出父类即可
    //4. 调用声明抛出异常的方法时，调用者必须对该异常进行处理(try-catch)，或者继续使用throws抛出

    //try-catch捕获并处理
    //throws对异常并没有真正处理，而是将异常报告给抛出异常方法的调用者，由调用者处理。如果真正要对异常进行处理，就需要try-catch
    public static void test3() throws InterruptedException {
        Config config = new Config();

        try {
            config.openConfig("config.txt");
            //一旦抛出异常,try后面的代码就不会执行了
            System.out.println("文件打开成功!");
        } catch (IOException e) {
            // 异常的处理方式
            System.out.println(e.getMessage()); // 只打印异常信息
            System.out.println(e); // 打印异常类型：异常信息
            System.out.flush();
            e.printStackTrace(); //打印异常信息最详细,还会打印调用堆栈
        }
        // 一旦异常被捕获处理了，此处的代码会执行
        System.out.println("异常如果被处理了，这里的代码也可以执行");

        Thread.sleep(10000);
    }

    //【注意事项】
    //1. try块内抛出异常位置之后的代码将不会被执行
    //2. 如果抛出异常类型与catch时异常类型不匹配，即异常不会被成功捕获，也就不会被处理，继续往外抛，直到JVM收到后中断程序----异常是按照类型来捕获的
    //3. try中可能会抛出多个不同的异常对象，则必须用多个catch来捕获----即多种异常，多次捕获
    //4. 如果多个异常的处理方式是完全相同, 也可以通过 | 进行异常的同时接收
    //5. 如果异常之间具有父子关系，一定是子类异常在前catch，父类异常在后catch，否则语法错误
    //6.可以通过一个catch捕获所有的异常，即多个异常，一次捕获(不推荐)
    //由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常
    //备注: catch 进行类型匹配的时候, 不光会匹配相同类型的异常对象, 也会捕捉目标异常类型的子类对象
    public static void test4() {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
            //因为Java中一旦一个异常被某个 catch 块捕获并处理后，后面的 catch 块将不再执行
            //所以如果是父类的捕捉在子类的前面,那么子类的捕捉就永远捕捉不了,也就没有了意义
        } catch (NullPointerException e) {
            e.printStackTrace();
        } catch (Exception e) { // Exception可以捕获到所有异常
            e.printStackTrace();
        }
//        catch (NullPointerException e) { //
//        }
        System.out.println("after try catch");
    }

    //finally
    //在写程序时，有些特定的代码，不论程序是否发生异常，都需要执行
    // 比如程序中打开的资源：网络连接、数据库连接、IO流等，在程序正常或者异常退出时，必须要对资源进进行回收
    // 另外，因为异常会引发程序的跳转，可能导致有些语句执行不到，finally就是用来解决这个问题的
    //问题：既然 finally 和 try-catch-finally 后的代码都会执行，那为什么还要有finally呢？
    //因为finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally)
    // 但是如果finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return
    //一般我们不建议在 finally 中写 return (被编译器当做一个警告)
    //所以在finally中进行一些资源清理的扫尾工作
    public static void test5() {
    }

    //【异常处理流程总结】
    //程序先执行 try 中的代码
    //如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配
    //如果找到匹配的异常类型, 就会执行 catch 中的代码
    //如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者
    //无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行)
    //如果上层调用者也没有处理的了异常, 就继续向上传递
    //一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止
    public static void test6() {
    }

    //自定义异常类
    //Java 中虽然已经内置了丰富的异常类, 但是并不能完全表示实际开发中所遇到的一些异常，此时就需要维护符合我们实际情况的异常结构
    //例如, 我们实现一个用户登陆功能
    public static void test7() {
        try {
            Login.loginInfo("admin", "123456");
        } catch (UserNameException e) {
            e.printStackTrace();
        } catch (PasswordException e) {
            e.printStackTrace();
        }

        //注意事项
        //自定义异常通常会继承自 Exception 或者 RuntimeException
        //继承自 Exception 的异常默认是受查异常
        //继承自 RuntimeException 的异常默认是非受查异常

        // 继承自 Exception 的自定义异常（受查异常）
        //受查异常（Checked Exception）：
        //继承自 Exception 类的异常被称为受查异常
        //受查异常在编译时会被强制检查，编译器要求必须对这些异常进行处理（即捕获或声明抛出），否则代码将无法编译通过
        //常见的受查异常包括 IOException、SQLException 等

        //2. 继承自 RuntimeException 的自定义异常（非受查异常）
        //非受查异常（Unchecked Exception）：
        //继承自 RuntimeException 类的异常被称为非受查异常
        //非受查异常在编译时不会被强制检查，编译器不要求对这些异常进行处理。你可以选择捕获和处理这些异常，也可以选择不处理，让它们传播
        //常见的非受查异常包括 NullPointerException、ArrayIndexOutOfBoundsException 等
    }

    public static void test8() {
        throw new MyUncheckedException("This is a custom unchecked exception");
    }

    public static void test9() {
    }

    public static void test10() {
    }

    public static void test11() {
    }

    public static void test12() {
    }

    public static void test13() {
    }

    public static void test14() {
    }

    public static void test15() {
    }

    public static void main(String[] args) throws InterruptedException {
//        test1();
//        test2();
//        test3();
//        test4();
//        test5();
//        test6();
//        test7();
        test8();
//        test9();
//        test10();
//        test11();
//        test12();
//        test13();
        System.out.println("test");
    }
}
